package com.szsh.aiot.common.utils;

import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjusters;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @Description:
 * @Date 2023/10/18: 14:11
 * @Author: LOADING>>>>>
 */

@Component
public class TimeTools {

    private static final int MONTH_PER_YEAR = 12;
    public static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final ZoneId ZONE = ZoneId.systemDefault();

    public static Date getNow() {
        return Date.from(LocalDateTime.now().atZone(ZONE).toInstant());
    }

    public static Date getFixedNow() {
        return Date.from(LocalDateTime.now().withNano(0).atZone(ZONE).toInstant());
    }

    public static Date getFirstDateOfMonth() {
        LocalDateTime firstDayOfMonth = LocalDateTime.now().atZone(ZONE).toLocalDate().withDayOfMonth(1).atStartOfDay();
        return Date.from(firstDayOfMonth.atZone(ZONE).toInstant());
    }

    public static Date getFirstDateOfMonth(Date date) {
        LocalDateTime firstDate = date.toInstant().atZone(ZONE).toLocalDate().withDayOfMonth(1).atStartOfDay();
        return Date.from(firstDate.atZone(ZONE).toInstant());
    }

    public static Date getLastDateOfMonth() {
        LocalDateTime nextMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).atTime(23, 59, 59, 0);
        return Date.from(nextMonth.atZone(ZONE).toInstant());
    }

    public static Date getLastDateOfMonth(Date date) {
        LocalDateTime nextMonth = date.toInstant().atZone(ZONE).toLocalDate().with(TemporalAdjusters.lastDayOfMonth()).atTime(23, 59, 59, 0);
        return Date.from(nextMonth.atZone(ZONE).toInstant());
    }

    public static Date getNextMonthExpireDate(Date subscribeDate) {
        int subDay = subscribeDate.toInstant().atZone(ZONE).getDayOfMonth();
        LocalDateTime nextMonth = LocalDate.now().plusMonths(1L).withDayOfMonth(subDay).atTime(23, 59, 59, 0);
        return Date.from(nextMonth.atZone(ZONE).toInstant());
    }

    public static Date getNextExpireDate(Date subscribeDate) {
        return getNextExpireDate(subscribeDate, getFixedNow());
    }

    public static Date getNextExpireDate(Date subscribeDate, Date specifyDate) {
        int subDay = subscribeDate.toInstant().atZone(ZONE).getDayOfMonth();
        int dayOfMonth = specifyDate.toInstant().atZone(ZONE).getDayOfMonth();
        int plusMonth = dayOfMonth > subDay ? 1 : 0;
        LocalDateTime nextMonth = specifyDate.toInstant().atZone(ZONE).toLocalDate().plusMonths((long)plusMonth).withDayOfMonth(subDay).atTime(23, 59, 59, 0);
        return Date.from(nextMonth.atZone(ZONE).toInstant());
    }

    public static Date getNextMonthDate(Date date) {
        return plusMonths(date, 1);
    }

    public static Date getNextMonthDate() {
        return plusMonths(1);
    }

    public static Date plusDays(int days) {
        return Date.from(LocalDateTime.now().atZone(ZONE).plusDays((long)days).toInstant());
    }

    public static Date plusDays(Date date, int days) {
        return Date.from(date.toInstant().atZone(ZONE).plusDays((long)days).toInstant());
    }

    public static Date plusMonths(int months) {
        return Date.from(LocalDateTime.now().plusMonths((long)months).atZone(ZONE).toInstant());
    }

    public static Date plusMonths(Date date, int months) {
        return Date.from(date.toInstant().atZone(ZONE).plusMonths((long)months).toInstant());
    }

    public static Date getDateTime(String time, String format) {
        TemporalAccessor dt = DateTimeFormatter.ofPattern(format).parseBest(time, LocalDateTime::from, LocalDate::from, LocalTime::from, YearMonth::from, MonthDay::from, Year::from);
        if (dt instanceof LocalDateTime) {
            return Date.from(((LocalDateTime)dt).atZone(ZONE).toInstant());
        } else if (dt instanceof LocalDate) {
            return Date.from(((LocalDate)dt).atStartOfDay(ZONE).toInstant());
        } else if (dt instanceof LocalTime) {
            return Date.from(((LocalTime)dt).atDate(LocalDate.now()).atZone(ZONE).toInstant());
        } else if (dt instanceof YearMonth) {
            return Date.from(((YearMonth)dt).atDay(1).atStartOfDay(ZONE).toInstant());
        } else if (dt instanceof MonthDay) {
            return Date.from(((MonthDay)dt).atYear(LocalDate.now().getYear()).atStartOfDay(ZONE).toInstant());
        } else if (dt instanceof Year) {
            return Date.from(((Year)dt).atDay(1).atStartOfDay(ZONE).toInstant());
        } else {
            throw new RuntimeException("Unsupported Format :" + format);
        }
    }

    public static Timestamp string2Time(String time) {
        return Timestamp.valueOf(LocalDateTime.parse(time, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }

    public static String time2String(Timestamp time) {
        return time.toLocalDateTime().format(DTF);
    }

    public static String time2String(Timestamp time, String strFormat) {
        return time.toLocalDateTime().format(DateTimeFormatter.ofPattern(strFormat));
    }

    public static String date2String(Date date, String strFormat) {
        return date.toInstant().atZone(ZONE).format(DateTimeFormatter.ofPattern(strFormat));
    }

    public static Timestamp timestampAddDays(Timestamp date, Integer days) {
        return new Timestamp(date.getTime() + TimeUnit.DAYS.toMillis((long)days));
    }

    public static Timestamp timestampAddHours(Timestamp date, Integer hours) {
        return new Timestamp(date.getTime() + TimeUnit.HOURS.toMillis((long)hours));
    }

    public static Date getFirstDateOfNextMonth(Date date) {
        LocalDateTime nextMonth = date.toInstant().atZone(ZONE).toLocalDate().plusMonths(1L).with(TemporalAdjusters.firstDayOfMonth()).atTime(0, 0, 0, 0);
        return Date.from(nextMonth.atZone(ZONE).toInstant());
    }

    public static List<Date> getDatesFromNow(Date endDate) {
        Date now = getNow();
        return endDate.after(now) ? getDates(now, endDate) : getDates(endDate, now);
    }

    public static List<Date> getDates(Date fromDate, Date endDate) {
        if (!endDate.after(fromDate)) {
            return Collections.emptyList();
        } else {
            int days = (int)getDaysBetween(fromDate, endDate);
            return (List) IntStream.range(0, days).mapToObj((i) -> {
                return plusDays(fromDate, i);
            }).collect(Collectors.toList());
        }
    }

    public static long getDaysBetween(Date fromDateInclusive, Date endDateInclusive) {
        LocalDateTime start = fromDateInclusive.toInstant().atZone(ZONE).toLocalDate().atStartOfDay();
        LocalDateTime end = endDateInclusive.toInstant().atZone(ZONE).toLocalDate().atStartOfDay();
        return Duration.between(start, end).abs().toDays() + 1L;
    }

    public static Date getDateEndTime(Date date) {
        LocalDateTime dateEnd = date.toInstant().atZone(ZONE).toLocalDate().atTime(23, 59, 59, 0);
        return Date.from(dateEnd.atZone(ZONE).toInstant());
    }

    private TimeTools() {
    }
}
